import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGEncodeParam;
import com.sun.image.codec.jpeg.JPEGImageEncoder;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * 图片压缩工具类
 *
 * @author billy
 */
public class ImageUtils {
    /** 项目配置文件的文件名 */
    private static final String CONFIG_PROPERTIES_FILE_NAME = "config.properties";
    /** 线程数 */
    private static final String KEY_THREAD_COUNT = "threadCount";
    /** 项目路径 */
    private static final String KEY_PROJECT_PATH = "projectPath";
    /** 原始图片路径 */
    private static final String KEY_SRC_IMAGE_PATH = "srcImagePath";
    /** 压缩后图片保存的路径 */
    private static final String KEY_OUT_IMAGE_PATH = "outImagePath";
    /** 压缩后图片的文件格式，<空白>（保留源文件格式）、jpg、png */
    private static final String KEY_OUT_IMAGE_FILE_SUFFIX = "outImageFileSuffix";
    /** 压缩图片后宽高的最大值，单位：px */
    private static final String KEY_MAX_BORDER_PX = "maxBorderPX";
    /** 压缩图片后文件的大小，单位：kb */
    private static final String KEY_FILE_MAX_LENGTH = "fileMaxLength";
    /** 添加的图片水印的图片路径 */
    private static final String KEY_IMAGE_WATER_MARK_PATH = "imageWaterMarkPath";
    /**
     * 添加文字水印的类型
     * 0：添加固定文字水印，文字取自于[textWaterMarkText]参数
     * 1：添加动态文字水印，文字取自于[当前图片文件名]
     * 2：添加动态文字水印，文字取自于[当前图片文件所在的文件夹名称]
     */
    private static final String KEY_TEXT_WATER_MARK_TYPE = "textWaterMarkType";
    /** 添加固定文字水印时的文字内容 */
    private static final String KEY_TEXT_WATER_MARK_TEXT = "textWaterMarkText";
    /** 添加固定文字水印时的文字的大小 */
    private static final String KEY_TEXT_WATER_MARK_TEXT_SIZE = "textWaterMarkTextSize";
    /** 添加文字水印的文字字体的颜色和透明度 */
    private static final Color COLOR_TEXT_WATER_MARK = new Color(255, 0, 0, 128);

    public static void main(String[] args) {
        //初始化,并获取参数配置
        final Map<String, String> configMap = initParamMap();
        if (configMap == null || configMap.isEmpty()) {
            System.err.println("读取配置文件失败，程序终止！");
            return;
        }
        compressImage(configMap);
    }

    private static void compressImage(final Map<String, String> configMap) {
        //线程数，最低2个
        int threadCount = Math.min(Math.max(parseInt(configMap.get(KEY_THREAD_COUNT), 2), 2),
                Runtime.getRuntime().availableProcessors());
        String srcImagePath = configMap.get(KEY_SRC_IMAGE_PATH);
        int textWaterMarkTextSize = parseInt(configMap.get(KEY_TEXT_WATER_MARK_TEXT_SIZE), 32);

        File fileDir = new File(srcImagePath);
        File[] files = fileDir.listFiles();
        if (files == null) {
            System.err.println("待压缩的图片文件夹不存在，压缩结束！");
            return;
        }
        //添加个空白行
        System.out.println();
        System.out.println("------>开始压缩图片，请稍候...");
        final List<Future<?>> futureList = new ArrayList<>();
        //创建线程池
        final ExecutorService threadPool = Executors.newFixedThreadPool(threadCount);
        //添加文字水印的文字字体和大小
        final Font fontTextWater = new Font("微软雅黑", Font.BOLD, textWaterMarkTextSize);
        for (File file : files) {
            if (file.isDirectory()) {
                File[] subFiles = file.listFiles();
                for (File subFile : subFiles) {
                    Future<?> future = threadPool.submit(() -> {
                        handleFile(subFile, configMap, fontTextWater);
                    });
                    futureList.add(future);
                }
            } else {
                Future<?> future = threadPool.submit(() -> {
                    handleFile(file, configMap, fontTextWater);
                });
                futureList.add(future);
            }
        }
        //检查每一个任务是否执行完成
        futureList.forEach(objectFuture -> {
            try {
                //获取线程执行返回后的结果，当线程为执行完成时，则一直处于阻塞状态
                objectFuture.get();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        //关闭线程池
        threadPool.shutdown();
        System.out.println("------>图片压缩完成");
    }

    /**
     * @param file          传入的图片文件对象
     * @param configMap     图片处理参数map
     * @param fontTextWater 文字水印时的字体
     */
    private static void handleFile(final File file, final Map<String, String> configMap,
                                   final Font fontTextWater) {
        String fileName = file.getName();
        String fileSuffix = getFileSuffix(fileName).toLowerCase();
        if (!fileSuffix.equals("jpg") && !fileSuffix.equals("jpeg") && !fileSuffix.equals("png")) {
            return;
        }
        //实际输出的文件完整路径
        String realOutImagePath = configMap.get(KEY_OUT_IMAGE_PATH) +
                file.getPath().replace(configMap.get(KEY_SRC_IMAGE_PATH), "");
        //根据参数来设置输出后的图片格式
        String outImageFileSuffix = configMap.get(KEY_OUT_IMAGE_FILE_SUFFIX);
        if (!isEmpty(outImageFileSuffix)) {
            realOutImagePath = realOutImagePath.substring(0, realOutImagePath.lastIndexOf(".") + 1)
                    + outImageFileSuffix;
        }
        File outFile = new File(realOutImagePath);
        File outParentFile = outFile.getParentFile();
        //父文件夹不存在时，则需要创建
        if (outParentFile != null && !outParentFile.exists()) {
            outParentFile.mkdirs();
        }
        //按指定宽高最大值并指定压缩后图片文件大小的方式来压缩图片
        try {
            if (handleSingleImage(file.getPath(), realOutImagePath, configMap, fontTextWater)) {
                System.out.println("[INFO] " + file.getName() + " 压缩成功");
            } else {
                System.err.println("[ERROR] " + file.getName() + " 压缩失败 -> ??????");
            }
        } catch (IOException e) {
            System.err.println("[ERROR] " + file.getName() + " 压缩失败 -> ?????? \r\n" + e.getMessage());
        }
    }

    /**
     * 初始化相关路径
     */
    private static Map<String, String> initParamMap() {
        Map<String, String> configMap = new HashMap<>();
        File file;
        String projectPath;
        try {
            file = new File("");
            projectPath = file.getCanonicalPath();
        } catch (Exception e) {
            projectPath = "D:" + File.separator + "压缩图片";
        }
        configMap.put(KEY_PROJECT_PATH, projectPath);
        //读取配置文件
        if (!readProperties(configMap)) {
            return null;
        }
        String srcImagePath = configMap.get(KEY_SRC_IMAGE_PATH);
        if (isEmpty(srcImagePath)) {
            srcImagePath = projectPath;
        }
        configMap.put(KEY_SRC_IMAGE_PATH, srcImagePath);
        String outImagePath = configMap.get(KEY_OUT_IMAGE_PATH);
        if (isEmpty(outImagePath) || srcImagePath.equals(outImagePath)) {
            outImagePath = srcImagePath + File.separator + "压缩后图片";
        }
        configMap.put(KEY_OUT_IMAGE_PATH, outImagePath);
        file = new File(outImagePath);
        if (!file.exists()) {
            file.mkdirs();
        }
        return configMap;
    }

    /**
     * @param srcImgPath    原图文件路径
     * @param outImgPath    处理后图片输出的路径
     * @param configMap     图片处理参数map
     * @param fontTextWater 文字水印时的字体
     * @return true：图片处理成功，false：图片处理失败
     */
    private static boolean handleSingleImage(String srcImgPath, String outImgPath,
                                             final Map<String, String> configMap,
                                             final Font fontTextWater)
            throws IOException {
        int maxBorderPX = parseInt(configMap.get(KEY_MAX_BORDER_PX), 0);
        int fileMaxLength = parseInt(configMap.get(KEY_FILE_MAX_LENGTH), 0);
        String imageWaterMarkPath = configMap.get(KEY_IMAGE_WATER_MARK_PATH);
        int textWaterMarkType = parseInt(configMap.get(KEY_TEXT_WATER_MARK_TYPE), 0);
        String textWaterMarkText = configMap.get(KEY_TEXT_WATER_MARK_TEXT);

        //先按图片最大宽高进行压缩
        BufferedImage newImage = getCompressImageBymaxBorderPX(srcImgPath, null, maxBorderPX,
                configMap.get(KEY_OUT_IMAGE_FILE_SUFFIX));
        //重复对图片进行精度压缩
        byte[] imageBytes = loopCompressImageQuality(srcImgPath, newImage, fileMaxLength);
        //图片水印处理
        if (!isEmpty(imageWaterMarkPath)) {
            File file = new File(imageWaterMarkPath);
            if (file.exists() && file.isFile()
                    && isContainsFileNameFilter(file.getName(), new String[]{"jpg", "jpeg", "png"})) {
                imageBytes = addImageWaterMark(null, imageBytes, imageWaterMarkPath);
            }
        }
        //文字水印的处理
        if (textWaterMarkType != -1) {
            String waterText = getTextWaterMarkText(srcImgPath, textWaterMarkType, textWaterMarkText);
            imageBytes = addTextWaterMark(null, imageBytes, waterText,
                    COLOR_TEXT_WATER_MARK, fontTextWater);
        }
        //将数据写入文件
        return writeBytesToFile(imageBytes, outImgPath);
    }

    /**
     * 将图片按照指定的图片尺寸压缩。当只设定宽和高其中一项时，则按该项值的作为宽高最大值进行等比缩放，
     * 若用户宽和高都有设置，则按用户的宽高来压缩（非等比压缩），并将压缩后的图片文件保存至指定位置。
     * 可读取图片路径或图片的byte[]，优先使用byte[]
     *
     * @param srcImgPath  源图片路径
     * @param srcImgBytes 源图片byte[]
     * @param outImgPath  输出的压缩图片的路径
     * @param newWidth    压缩后的图片宽
     * @param newHeight   压缩后的图片高
     */
    public static boolean compressImageToFile(String srcImgPath, byte[] srcImgBytes,
                                              String outImgPath, int newWidth, int newHeight,
                                              String imageSuffix)
            throws IOException {
        BufferedImage newImage = getCompressImageByWidthHeight(srcImgPath, srcImgBytes,
                newWidth, newHeight, imageSuffix);
        // 调用方法输出图片文件
        return outputImageFile(outImgPath, newImage);
    }

    /**
     * 将图片按照指定的图片尺寸压缩，当只设定宽和高其中一项时，则按该项值的作为宽高最大值进行等比缩放，
     * 若用户宽和高都有设置，则按用户的宽高来压缩（非等比压缩），并返回压缩后的图片byte[]。
     * 可读取图片路径或图片的byte[]，优先使用byte[]
     *
     * @param srcImgPath  源图片路径
     * @param srcImgBytes 源图片byte[]
     * @param newWidth    压缩后的图片宽
     * @param newHeight   压缩后的图片高
     */
    public static byte[] compressImageToBytes(String srcImgPath, byte[] srcImgBytes,
                                              int newWidth, int newHeight, String imageSuffix)
            throws IOException {
        //压缩图片
        BufferedImage newImage = getCompressImageByWidthHeight(srcImgPath, srcImgBytes,
                newWidth, newHeight, imageSuffix);
        //输出压缩后的byte[]
        return getCompressImageBytes(newImage, srcImgPath);
    }

    /**
     * 按指定的压缩比例来压缩图片的尺寸大小（等比缩放），并将压缩后的图片文件保存至指定位置。
     * 当压缩比例<=0，则默认使用原图尺寸。
     * 可读取图片路径或图片的byte[]，优先使用byte[]
     *
     * @param srcImgPath   源图片路径
     * @param srcImgBytes  源图片byte[]
     * @param outImgPath   输出的压缩图片的路径
     * @param compressRate 图片压缩比例 0 - 1
     */
    public static boolean compressImageToFile(String srcImgPath, byte[] srcImgBytes,
                                              String outImgPath, float compressRate,
                                              String imageSuffix)
            throws IOException {
        BufferedImage newImage = getCompressImageByRate(srcImgPath, srcImgBytes, compressRate, imageSuffix);
        // 调用方法输出图片文件
        return outputImageFile(outImgPath, newImage);
    }

    /**
     * 按指定的压缩比例来压缩图片的尺寸大小（等比缩放），并返回压缩后的图片byte[]。
     * 当压缩比例<=0，则默认使用原图尺寸。
     * 可读取图片路径或图片的byte[]，优先使用byte[]
     *
     * @param srcImgPath   源图片路径
     * @param srcImgBytes  源图片byte[]
     * @param compressRate 图片压缩比例 0 - 1，超出范围则不处理，直接return
     */
    public static byte[] compressImageToBytes(String srcImgPath, byte[] srcImgBytes,
                                              float compressRate, String imageSuffix)
            throws IOException {
        BufferedImage newImage = getCompressImageByRate(srcImgPath, srcImgBytes, compressRate, imageSuffix);
        //输出压缩后的byte[]
        return getCompressImageBytes(newImage, srcImgPath);
    }

    /**
     * 指定长或者宽的最大值来压缩图片（等比缩放），并将压缩后的图片文件保存至指定位置。
     * 当maxBorderPX=0，则默认使用原图尺寸。
     * 可读取图片路径或图片的byte[]，优先使用byte[]
     *
     * @param srcImgPath  源图片路径
     * @param srcImgBytes 源图片byte[]
     * @param outImgPath  输出的压缩图片的路径
     * @param maxBorderPX 长或者宽的最大值
     */
    public static boolean compressImageToFile(String srcImgPath, byte[] srcImgBytes,
                                              String outImgPath, int maxBorderPX, String imageSuffix)
            throws IOException {
        //压缩图片
        BufferedImage newImage = getCompressImageBymaxBorderPX(srcImgPath, srcImgBytes, maxBorderPX, imageSuffix);
        // 调用方法输出图片文件
        return outputImageFile(outImgPath, newImage);
    }

    /**
     * 指定长或者宽的最大值来压缩图片（等比缩放），并返回压缩后的图片byte[]。
     * 当maxBorderPX=0，则默认使用原图尺寸。
     * 可读取图片路径或图片的byte[]，优先使用byte[]
     *
     * @param srcImgPath  源图片路径
     * @param srcImgBytes 源图片byte[]
     * @param maxBorderPX 长或者宽的最大值，若设置的值小于0或大于图片本身的宽高，则不处理，直接return
     */
    public static byte[] compressImageToBytes(String srcImgPath, byte[] srcImgBytes,
                                              int maxBorderPX, String imageSuffix)
            throws IOException {
        //压缩图片
        BufferedImage newImage = getCompressImageBymaxBorderPX(srcImgPath, srcImgBytes, maxBorderPX, imageSuffix);
        //输出压缩后的byte[]
        return getCompressImageBytes(newImage, srcImgPath);
    }

    /**
     * 对指定图片进行尺寸大小和文件大小（KB）双重压缩（等比缩放），
     * 尺寸大小压缩以宽高最大值maxBorderPX为标准来进行等比压缩，并将压缩后的图片文件保存至指定位置。
     * 当maxBorderPX=0，则默认使用原图尺寸。
     * 可读取图片路径或图片的byte[]，优先使用byte[]
     *
     * @param srcImgPath    源图片路径
     * @param srcImgBytes   源图片byte[]
     * @param maxBorderPX   压缩后宽高的最大值，等比压缩
     * @param fileMaxLength 压缩后图片的文件大小，单位为KB
     */
    public static boolean compressImageToFileByFileLength(String srcImgPath, byte[] srcImgBytes,
                                                          String outImgPath,
                                                          int maxBorderPX, int fileMaxLength,
                                                          String imageSuffix)
            throws IOException {
        //先按图片最大宽高进行压缩
        BufferedImage newImage = getCompressImageBymaxBorderPX(srcImgPath, srcImgBytes, maxBorderPX, imageSuffix);
        //重复对图片进行精度压缩
        byte[] imageBytes = loopCompressImageQuality(srcImgPath, newImage, fileMaxLength);
        //将数据写入文件
        return writeBytesToFile(imageBytes, outImgPath);
    }

    /**
     * 对指定图片进行尺寸大小和文件大小（KB）双重压缩（等比缩放），
     * 尺寸大小压缩以宽高最大值maxBorderPX为标准来进行等比压缩，并返回压缩后的图片byte[]。
     * 当maxBorderPX=0，则默认使用原图尺寸。
     * 可读取图片路径或图片的byte[]，优先使用byte[]
     *
     * @param srcImgPath      源图片路径
     * @param srcImgBytes     源图片byte[]
     * @param maxBorderPX     压缩后宽高的最大值，等比压缩
     * @param filemaxBorderPX 压缩后图片的文件大小，单位为KB
     */
    public static byte[] compressImageToBytesByFileLength(String srcImgPath, byte[] srcImgBytes,
                                                          int maxBorderPX, int filemaxBorderPX,
                                                          String imageSuffix)
            throws IOException {
        //先按图片最大宽高进行压缩
        BufferedImage newImage = getCompressImageBymaxBorderPX(srcImgPath, srcImgBytes, maxBorderPX, imageSuffix);
        //重复对图片进行精度压缩
        return loopCompressImageQuality(srcImgPath, newImage, filemaxBorderPX);
    }

    /**
     * 按指定的宽和高来压缩图片，当只设定宽和高其中一项时，则按该项值的作为宽高最大值进行等比缩放，
     * 若用户宽和高都有设置，则按用户的宽高来压缩（非等比压缩），
     * 可读取图片路径或图片的byte[]，优先使用byte[]
     *
     * @param srcImgPath  源图片路径
     * @param srcImgBytes 源图片转化的byte[]
     * @param newWidth    压缩后图片的宽
     * @param newHeight   压缩后图片的高
     */
    private static BufferedImage getCompressImageByWidthHeight(String srcImgPath,
                                                               byte[] srcImgBytes,
                                                               int newWidth, int newHeight,
                                                               String imageSuffix) {
        // 得到图片
        BufferedImage oldImage = loadImage(srcImgPath, srcImgBytes);
        if (oldImage == null) {
            return null;
        }
        //若宽高都为0，则默认尺寸与原图一样
        if (newWidth == 0 && newHeight == 0) {
            newWidth = oldImage.getWidth();
            newHeight = oldImage.getHeight();
        } else {
            //当只设定宽和高其中一项时，则按该项值的作为宽高最大值进行等比缩放
            if (newWidth == 0) {
                int[] newSizes = getAfterCompressImageSizeByMaxBorderPX(oldImage, newHeight);
                newWidth = newSizes[0];
                newHeight = newSizes[1];
            } else if (newHeight == 0) {
                int[] newSizes = getAfterCompressImageSizeByMaxBorderPX(oldImage, newWidth);
                newWidth = newSizes[0];
                newHeight = newSizes[1];
            }
        }
        //压缩图片
        return disposeImage(srcImgPath, oldImage, newWidth, newHeight, imageSuffix);
    }

    /**
     * 按指定的压缩比例来压缩图片的尺寸大小，当压缩比例<=0，则默认使用原图尺寸。
     * 可读取图片路径或图片的byte[]，优先使用byte[]
     *
     * @param srcImgPath   源图片路径
     * @param srcImgBytes  源图片转化的byte[]
     * @param compressRate 图片压缩比例
     */
    private static BufferedImage getCompressImageByRate(String srcImgPath, byte[] srcImgBytes,
                                                        float compressRate, String imageSuffix) {
        // 得到图片
        BufferedImage oldImage = loadImage(srcImgPath, srcImgBytes);
        if (oldImage == null) {
            return null;
        }
        //若比例设置超出范围，则默认尺寸与原图一样
        if (compressRate <= 0) {
            compressRate = 1.0f;
        }
        // 新图的宽
        int newWidth = Math.round(oldImage.getWidth() * compressRate);
        // 新图的高
        int newHeight = Math.round(oldImage.getHeight() * compressRate);
        //压缩图片
        return disposeImage(srcImgPath, oldImage, newWidth, newHeight, imageSuffix);
    }

    /**
     * 按指定的宽高最大值来等比例压缩图片，当maxBorderPX=0，则默认使用原图尺寸。
     * 可读取图片路径或图片的byte[]，优先使用byte[]
     *
     * @param srcImgPath  源图片路径
     * @param srcImgBytes 源图片转化的byte[]
     * @param maxBorderPX 长或者宽的最大值
     */
    private static BufferedImage getCompressImageBymaxBorderPX(String srcImgPath,
                                                               byte[] srcImgBytes,
                                                               int maxBorderPX,
                                                               String imageSuffix) {
        // 得到图片
        BufferedImage oldImage = loadImage(srcImgPath, srcImgBytes);
        if (oldImage == null) {
            return null;
        }
        int[] newSizes = getAfterCompressImageSizeByMaxBorderPX(oldImage, maxBorderPX);
        //压缩图片
        return disposeImage(srcImgPath, oldImage, newSizes[0], newSizes[1], imageSuffix);
    }

    /**
     * 对指定的BufferedImage进行图片质量的压缩
     */
    private static byte[] loopCompressImageQuality(String srcImgPath, final BufferedImage image, int fileMaxLength)
            throws IOException {
        if (image == null) {
            return null;
        }
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            ImageIO.write(image, getFileSuffix(srcImgPath), baos);
            int fileLength = baos.toByteArray().length / 1024;
            //System.out.println("file.length():" + fileLength + "KB，fileMaxLength:" + fileMaxLength + "KB");
            //若图片在压缩尺寸后，在压缩精度前图片文件大小已经符合要求了，则不压缩精度
            if (fileLength <= fileMaxLength) {
                return baos.toByteArray();
            }
            //初始压缩质量为0.7，因为压缩图片尺寸时，默认压缩质量为0.75
            float quality = 0.7f * fileMaxLength / fileLength;
            //循环压缩图片，直到小于指定大小，压缩精度最低为0.05
            while (fileLength > fileMaxLength && quality >= 0.05f) {
                baos.reset();
                compressImageQuality(image, baos, quality);
                //获取文件文件大小
                fileLength = baos.toByteArray().length / 1024;
                //System.out.println("quality:" + quality + ",file.length():" + fileLength + "KB");
                quality -= 0.05f;
            }
            return baos.toByteArray();
        } finally {
            close(baos);
        }
    }

    /**
     * 对指定的BufferedImage进行图片质量的压缩
     */
    private static void compressImageQuality(BufferedImage image,
                                             ByteArrayOutputStream baos, float quality) throws IOException {
        //将图片按JPEG压缩，保存到out中
        JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(baos);
        JPEGEncodeParam jep = JPEGCodec.getDefaultJPEGEncodeParam(image);
        //压缩质量
        jep.setQuality(quality, true);
        encoder.encode(image, jep);
    }

    /**
     * 返回用指定长或者宽的最大值来压缩图片时压缩后图片的宽高。
     * 若宽高最大值为0，则默认尺寸与原图一样；若宽高最大值小于原图的尺寸，则默认尺寸与原图一样
     */
    private static int[] getAfterCompressImageSizeByMaxBorderPX(BufferedImage image, int maxBorderPX) {
        // 得到源图宽
        int oldWidth = image.getWidth();
        // 得到源图长
        int oldHeight = image.getHeight();
        int newWidth, newHeight;
        //若宽高最大值为0，则默认尺寸与原图一样
        if (maxBorderPX <= 0) {
            newWidth = oldWidth;
            newHeight = oldHeight;
        } else {
            // 当用户指定的宽高最大值不为0时，则按maxBorderPX等比计算新的宽高
            if (oldWidth > oldHeight) {
                //取新旧宽度的最小值
                newWidth = Math.min(oldWidth, maxBorderPX);
                newHeight = Math.round(oldHeight * ((float) newWidth / oldWidth));
            } else {
                //取新旧高度的最小值
                newHeight = Math.min(oldHeight, maxBorderPX);
                newWidth = Math.round(oldWidth * ((float) newHeight / oldHeight));
            }
        }
        return new int[]{newWidth, newHeight};
    }

    /**
     * 图片文件读取，可读取图片路径或图片的byte[]，优先使用byte[]
     */
    private static BufferedImage loadImage(String srcImgPath, byte[] srcImgBytes) {
        if (isEmpty(srcImgPath) && srcImgBytes == null) {
            return null;
        }
        InputStream input = null;
        try {
            if (srcImgBytes != null) {
                input = new ByteArrayInputStream(srcImgBytes);
            } else {
                input = new FileInputStream(srcImgPath);
            }
            return ImageIO.read(input);
        } catch (IOException e) {
            System.out.println(srcImgPath + " 读取图片文件出错\r\n" + e.getMessage());
        } finally {
            close(input);
        }
        return null;
    }

    /**
     * 处理图片并返回压缩后尺寸的图片，若传递的oldImage!=null，则直接使用它，否则根据srcImgPath重新loadImage
     */
    private static BufferedImage disposeImage(String srcImgPath, BufferedImage oldImage,
                                              int newWidth, int newHeight, String imageSuffix) {
        // 得到图片
        //int oldWidth = image.getWidth();
        // 得到源图宽
        //int oldHeight = image.getHeight();
        String suffix = getFileSuffix(srcImgPath);
        //输出的图片的格式，默认为jpg
        int imageType = BufferedImage.TYPE_INT_RGB;
        //若有指定图片后缀名，则用指定的后缀名
        if (!isEmpty(imageSuffix) && imageSuffix.equalsIgnoreCase("png")) {
            imageType = BufferedImage.TYPE_INT_ARGB;
        }
        BufferedImage newImage = new BufferedImage(newWidth, newHeight, imageType);
        //Graphics2D g = newImage.createGraphics();
        // 从原图上取颜色绘制新图
        //g.drawImage(image, 0, 0, oldWidth, oldHeight, null);
        //g.dispose();
        // 根据图片尺寸压缩比得到新图的尺寸
        newImage.getGraphics().drawImage(
                oldImage.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH),
                0, 0, null);
        //下面这方法压缩的图片存在颗粒感
        //newImage.getGraphics().drawImage(oldImage, 0, 0, newWidth, newHeight, null);
        return newImage;
    }

    /**
     * 将图片文件输出到指定的路径
     */
    private static boolean outputImageFile(String outImgPath, BufferedImage image)
            throws IOException {
        if (image == null) {
            return false;
        }
        // 判断输出的文件夹路径是否存在，不存在则创建
        File file = new File(outImgPath);
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        // 输出到文件流
        return ImageIO.write(image, getFileSuffix(outImgPath), new File(outImgPath));
    }

    /**
     * 获取压缩后图片的byte[]
     */
    private static byte[] getCompressImageBytes(BufferedImage image, String srcImgPath)
            throws IOException {
        if (image == null) {
            return null;
        }
        ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
        ImageIO.write(image, getFileSuffix(srcImgPath), baos);
        try {
            return baos.toByteArray();
        } finally {
            close(baos);
        }
    }

    /**
     * 将byte[]数据写入文件中
     */
    public static byte[] convertInputStreamToBytes(InputStream input) throws IOException {
        if (input == null) {
            return null;
        }
        ByteArrayOutputStream baos = null;
        try {
            baos = new ByteArrayOutputStream(1024);
            byte[] buffer = new byte[1024 * 8];
            int len;
            while ((len = input.read(buffer)) != -1) {
                baos.write(buffer, 0, len);
            }
            baos.flush();
            return baos.toByteArray();
        } finally {
            close(baos);
            close(input);
        }
    }

    /**
     * 将byte[]数据写入文件中
     */
    public static boolean writeBytesToFile(byte[] bytes, String outFilePath)
            throws IOException {
        if (bytes == null) {
            return false;
        }
        FileOutputStream fos = new FileOutputStream(outFilePath);
        BufferedOutputStream bos = new BufferedOutputStream(fos, 8 * 1024);
        //将数据写入文件
        bos.write(bytes);
        bos.flush();
        bos.close();
        return true;
    }

    /**
     * 获取目录的文件列表（递归）
     *
     * @param dirPath         获取文件的根目录
     * @param isIncludeSubDir 是否包含根目录的子目录
     */
    private static void getFileList(final List<File> fileList, final String dirPath, final boolean isIncludeSubDir) {
        if (isEmpty(dirPath)) {
            return;
        }
        try {
            File dirFile = new File(dirPath);
            if (!dirFile.exists()) {
                return;
            }
            File[] files = dirFile.listFiles();
            for (File file : files) {
                if (file.isFile()) {
                    fileList.add(file);
                } else {
                    if (isIncludeSubDir) {
                        getFileList(fileList, file.getPath(), isIncludeSubDir);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /** 将指定的多个文件筛选字符串（"jpg,jpeg,png"）拆分成一个String[]，数据中每个item就是单个的文件帅选项 */
    private static String[] getFileNames(String fileNameFilter) {
        if (isEmpty(fileNameFilter)) {
            return null;
        }
        if (fileNameFilter.contains(",")) {
            return fileNameFilter.split(",");
        } else {
            return new String[]{fileNameFilter};
        }
    }

    /** 判断指定的文件名的扩展名是否在filters数组的能够找到，只要符合一个即返回true */
    private static boolean isContainsFileNameFilter(String fileName, String[] filters) {
        if (isEmpty(fileName) || filters == null) {
            return true;
        }
        for (String filter : filters) {
            if (fileName.endsWith("." + filter)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取指定文件的文件名，不含后缀名
     */
    private static String getFileOnlyName(String filePath) {
        if (isEmpty(filePath)) {
            return null;
        }
        try {
            //若文件为完整路径
            if (filePath.contains(File.separator)) {
                filePath = filePath.substring(filePath.lastIndexOf(File.separator) + 1);
            }
            return filePath.substring(0, filePath.lastIndexOf("."));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取指定文件的文件名，不含后缀名
     */
    private static String getFileParentName(String filePath) {
        if (isEmpty(filePath)) {
            return null;
        }
        File file = new File(filePath);
        if (!file.exists()) {
            return null;
        }
        String parentPath = file.getParent();
        if (parentPath == null) {
            return null;
        }
        return parentPath.substring(parentPath.lastIndexOf(File.separator) + 1);
    }

    /**
     * 获取指定文件名的后缀名，若获取失败，则默认返回jpg
     */
    private static String getFileSuffix(String filePath) {
        if (isEmpty(filePath)) {
            return "jpg";
        }
        try {
            return filePath.substring(filePath.lastIndexOf(".") + 1);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "jpg";
    }

    /**
     * 获取添加文字水印的文字内容
     */
    private static String getTextWaterMarkText(String srcImagePath, int textWaterMarkType,
                                               String textWaterMarkText) {
        switch (textWaterMarkType) {
            case 0:
                return textWaterMarkText;
            case 1:
                return getFileOnlyName(srcImagePath);
            case 2:
                return getFileParentName(srcImagePath);
            default:
                return "";
        }
    }

    public static boolean isEmpty(CharSequence s) {
        if (s == null || s.length() == 0) {
            return true;
        }
        return false;
    }

    /**
     * 将指定的字符串转为int
     */
    public static int parseInt(String s, int defValue) {
        if (s == null || s.length() == 0) {
            return defValue;
        }
        try {
            return Integer.parseInt(s);
        } catch (NumberFormatException e) {
            return defValue;
        }
    }

    /**
     * 读取配置文件
     */
    private static boolean readProperties(Map<String, String> configMap) {
        InputStream is = null;
        try {
//            is = MyUtils.class.getClassLoader().getResourceAsStream(PROPERTIES_FILE_NAME);
            File file = new File(configMap.get(KEY_PROJECT_PATH), CONFIG_PROPERTIES_FILE_NAME);
            if (!file.exists() || !file.isFile()) {
                System.out.println("错误：无法找到配置文件[" + CONFIG_PROPERTIES_FILE_NAME + "]");
                return false;
            }
            is = new FileInputStream(file);
            Properties p = new Properties();
            p.load(is);

            configMap.put(KEY_THREAD_COUNT, p.getProperty(KEY_THREAD_COUNT, "2"));
            configMap.put(KEY_SRC_IMAGE_PATH, getPropertiesString(p.getProperty(KEY_SRC_IMAGE_PATH, "")));
            configMap.put(KEY_OUT_IMAGE_PATH, getPropertiesString(p.getProperty(KEY_OUT_IMAGE_PATH, "")));
            configMap.put(KEY_OUT_IMAGE_FILE_SUFFIX, p.getProperty(KEY_OUT_IMAGE_FILE_SUFFIX, ""));
            configMap.put(KEY_MAX_BORDER_PX, p.getProperty(KEY_MAX_BORDER_PX, "0"));
            configMap.put(KEY_FILE_MAX_LENGTH, p.getProperty(KEY_FILE_MAX_LENGTH, "0"));
            configMap.put(KEY_IMAGE_WATER_MARK_PATH, getPropertiesString(p.getProperty(KEY_IMAGE_WATER_MARK_PATH, "")));
            configMap.put(KEY_TEXT_WATER_MARK_TYPE, p.getProperty(KEY_TEXT_WATER_MARK_TYPE, "-1"));
            configMap.put(KEY_TEXT_WATER_MARK_TEXT, getPropertiesString(p.getProperty(KEY_TEXT_WATER_MARK_TEXT, "")));
            configMap.put(KEY_TEXT_WATER_MARK_TEXT_SIZE, p.getProperty(KEY_TEXT_WATER_MARK_TEXT_SIZE, "32"));
            return true;
        } catch (Exception e) {
            System.out.println("错误：读取配置文件" + CONFIG_PROPERTIES_FILE_NAME + "发生错误" + e.getMessage());
        } finally {
            close(is);
        }
        return false;
    }

    /**
     * 将从Properties配置文件中读取的信息转码（避免中文乱码）
     * 注：无论系统的默认编码是什么，在读取properties文件时使用的ISO-8859-1编码。
     */
    public static String getPropertiesString(String s) {
        if (isEmpty(s)) {
            return "";
        }
        try {
            return new String(s.getBytes("ISO-8859-1"), "GBK");
        } catch (UnsupportedEncodingException e) {
            return "";
        }
    }

    /**
     * 关闭对象
     */
    public static void close(AutoCloseable closeable) {
        if (closeable == null) {
            return;
        }
        try {
            closeable.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 为图片添加图片水印
     *
     * @param srcImagePath   原图（文件路径）
     * @param srcImageBytes  原图（文件的byte[]），处理中原图优先使用byte[]
     * @param waterImagePath 水印图片
     * @return 添加水印图片后的byte[]
     */
    public static byte[] addImageWaterMark(String srcImagePath, byte[] srcImageBytes, String waterImagePath)
            throws IOException {
        //获取原图的img对象
        Image srcImg = loadImage(srcImagePath, srcImageBytes);
        int width = srcImg.getWidth(null);//水印宽度
        int height = srcImg.getHeight(null);//水印高
        BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = bi.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g.drawImage(srcImg.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null);
        ImageIcon imgIcon = new ImageIcon(waterImagePath);
        Image con = imgIcon.getImage();
        float clarity = 1f;//透明度
        g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, clarity));
        g.drawImage(con, 15, 15, null);//水印的位置
        g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
        g.dispose();
        //返回添加水印图片后的byte[]
        return getCompressImageBytes(bi, null);
    }

    /**
     * 为图片添加文字水印
     *
     * @param srcImagePath  原图（文件路径）
     * @param srcImageBytes 原图（文件的byte[]），处理中原图优先使用byte[]
     * @param waterText     水印文字
     * @param color         水印颜色
     * @param font          水印字体
     * @return 添加水印图片后的byte[]
     */
    public static byte[] addTextWaterMark(String srcImagePath, final byte[] srcImageBytes,
                                          String waterText, final Color color, final Font font)
            throws IOException {
        //获取原图的img对象
        Image srcImg = loadImage(srcImagePath, srcImageBytes);
        int srcImgWidth = srcImg.getWidth(null);
        int srcImgHeight = srcImg.getHeight(null);
        BufferedImage buffImg = new BufferedImage(srcImgWidth, srcImgHeight,
                BufferedImage.TYPE_INT_RGB);
        Graphics2D g = buffImg.createGraphics();
        g.drawImage(srcImg, 0, 0, srcImgWidth, srcImgHeight, null);
        g.setColor(color);
        g.setFont(font);
        //设置水印的坐标
        int x = srcImgWidth - (g.getFontMetrics(g.getFont()).charsWidth(
                waterText.toCharArray(), 0, waterText.length()) + 15);
        int y = srcImgHeight - 15;
        g.drawString(waterText, x, y);  //加水印
        g.dispose();
        //返回添加水印图片后的byte[]
        return getCompressImageBytes(buffImg, null);
    }
}
